package com.ct.dao.searching.hibernate;

import com.ct.model.common.Entity;
import com.ct.dao.searching.Query;

import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Map;
import java.util.HashMap;
import org.apache.commons.lang.StringUtils;

/**
 * Created by IntelliJ IDEA.
 * User: nmkravchenko
 * Date: 22.10.2009
 * Time: 17:52:11
 * To change this template use File | Settings | File Templates.
 */
public class HqlQuery<E extends Entity> implements Query<E> {

    private final static Pattern FROM_PATTERN = Pattern.compile(
            "(?:(from\\s*.*)\\s+order\\s*by.*|(from\\s*.*))",
            Pattern.CASE_INSENSITIVE);
    private final static Pattern FETCH_PATTERN = Pattern
            .compile(" fetch", Pattern.CASE_INSENSITIVE);
    private String _queryString;
    private String _basePart;
    private String _countQuery;
    private boolean _cacheable = false;
    private String _cacheRegion;
    private boolean _useRowCount = true;
    private boolean _sqlCountQuery = false;

    private Map<String, Object> _parameters = new HashMap<String, Object>();

    public HqlQuery(String aBasePart) {
        _basePart = aBasePart;
        _queryString = aBasePart;
    }

    public HqlQuery(CharSequence aBasePart, CharSequence aParamName, Object aParamValue) {
        this(aBasePart, asMapTypeSafe(String.class, Object.class,
                aParamName, aParamValue));
    }

    public HqlQuery(CharSequence aBasePart, Map<String, Object> aParameters) {
        _basePart = aBasePart.toString();
        _parameters = aParameters;
        _queryString = aBasePart.toString();
    }

    public HqlQuery(CharSequence aBasePart, CharSequence aOrderClause,
            Map<String, Object> aParameters) {
        _basePart = aBasePart.toString();
        _queryString = aBasePart + " " + aOrderClause;
        _parameters = aParameters;
    }

    public HqlQuery(CharSequence aBasePart, CharSequence aOrderClause) {
        _basePart = aBasePart.toString();
        _queryString = aBasePart + " " + aOrderClause;
    }

    public String getQueryString() {
        return _queryString;
    }

    public String getBasePart() {
        return _basePart;
    }

    public boolean isSqlCountQuery() {
        return _sqlCountQuery;
    }

    public void setSqlCountQuery(boolean sqlCountQuery) {
        this._sqlCountQuery = sqlCountQuery;
    }

    public HqlQuery<E> setOrderClause(CharSequence aOrderClause) {
        _queryString = _basePart + aOrderClause;
        return this;
    }

    public Map<String, Object> getParameters() {
        return _parameters;
    }

    public String getCountQuery() {
        if (_countQuery == null && _queryString != null) {
            _countQuery = prepareCountQuery();
        }
        return _countQuery;
    }

    /**
     * @return hql count query string based on the source hql query;<br/>
     *         empty string if the original query contains group by clause;<br/>
     *         <code>null</code> if the source query is empty.
     */
    public String prepareCountQuery() {
        if (_queryString == null || _queryString.length() == 0) {
            return null;
        }
        if (_queryString.toLowerCase().matches(".*group\\s*by.*")) {
            return "";
        }
        Matcher matcher = FROM_PATTERN.matcher(_queryString);
        if (!matcher.find()) {
            throw new IllegalStateException(
                    "Cannot extract count query from[" + _queryString + "]");
        }
        String fromStr = getFirstNotEmpty(matcher.group(1), matcher.group(2), "");
        return String.format("select count(%1$s) %2$s", "*",
                FETCH_PATTERN.matcher(fromStr).replaceAll(""));
    }

    private static <E, T> Map<E, T> asMapTypeSafe(Class<E> aKeyType, Class<T> aValType, Object... aObjects) {
        if (aObjects.length < 1 || aObjects.length % 2 != 0) {
            throw new IllegalArgumentException();
        }
        Map<E, T> retVal = new HashMap<E, T>();
        for (int i = 0; i < aObjects.length; i += 2) {
            E key = (E) aObjects[i];
            T val = (T) aObjects[i + 1];
            if (key != null && !aKeyType.isInstance(key)) {
                throw new ClassCastException("Wrong key type " + key.getClass());
            }
            if (val != null && !aValType.isInstance(val)) {
                throw new ClassCastException("Wrong value type " + val.getClass());
            }
            retVal.put(key, val);
        }
        return retVal;
    }

    private String getFirstNotEmpty(String... aStr) {
        if (aStr == null) {
            return null;
        }
        for (String str : aStr) {
            if (StringUtils.isNotBlank(str)) {
                return str;
            }
        }
        return null;
    }

    public HqlQuery<E> setCountQuery(CharSequence aCountQuery) {
        _countQuery = aCountQuery.toString();
        return this;
    }

    public HqlQuery<E> setSqlCountQuery(CharSequence aCountQuery) {
        _countQuery = aCountQuery.toString();
        _sqlCountQuery = true;
        return this;
    }

    public boolean isCacheable() {
        return _cacheable;
    }

    public void setCacheable(boolean aCacheable) {
        _cacheable = aCacheable;
    }

    public String getCacheRegion() {
        return _cacheRegion;
    }

    public void setCacheRegion(String aCacheRegion) {
        _cacheRegion = aCacheRegion;
    }

    public void disableUseRowCount() { _useRowCount = false; }
    public boolean isEnabledUseRowCount() { return _useRowCount; }

    public String toString() {
        return new StringBuilder("HqlQuery [ Query=").append(_queryString)
                .append(", Params=")
                .append(_parameters).append(", CountQuery=").append(getCountQuery())
                .append("]")
                .toString();
    }
}

